package com.pudu.pudusclient;

import android.app.PendingIntent;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbDeviceConnection;
import android.hardware.usb.UsbManager;
import android.os.Binder;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Message;
import android.util.Log;

import com.felhr.usbserial.CDCSerialDevice;
import com.felhr.usbserial.UsbSerialDevice;
import com.felhr.usbserial.UsbSerialInterface;
import com.pudu.mydemo.view.rplidar_node_t;
import com.pudu.pdrobot.App;
import com.sensors.Lidar;

import org.json.JSONArray;
import org.json.JSONObject;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class HLSRadarService extends Service {


        public static final String ACTION_USB_READY = "com.felhr.connectivityservices.USB_READY";
        public static final String ACTION_USB_ATTACHED = "android.hardware.usb.action.USB_DEVICE_ATTACHED";
        public static final String ACTION_USB_DETACHED = "android.hardware.usb.action.USB_DEVICE_DETACHED";
        public static final String ACTION_USB_NOT_SUPPORTED = "com.felhr.usbservice.USB_NOT_SUPPORTED";
        public static final String ACTION_NO_USB = "com.felhr.usbservice.NO_USB";
        public static final String ACTION_USB_PERMISSION_GRANTED = "com.felhr.usbservice.USB_PERMISSION_GRANTED";
        public static final String ACTION_USB_PERMISSION_NOT_GRANTED = "com.felhr.usbservice.USB_PERMISSION_NOT_GRANTED";
        public static final String ACTION_USB_DISCONNECTED = "com.felhr.usbservice.USB_DISCONNECTED";
        public static final String ACTION_CDC_DRIVER_NOT_WORKING = "com.felhr.connectivityservices.ACTION_CDC_DRIVER_NOT_WORKING";
        public static final String ACTION_USB_DEVICE_NOT_WORKING = "com.felhr.connectivityservices.ACTION_USB_DEVICE_NOT_WORKING";
        public static final int HLS_MESSAGE_FROM_SERIAL_PORT = 100;
        public static final int CTS_CHANGE = 1;
        public static final int DSR_CHANGE = 2;
        private static final String ACTION_USB_PERMISSION = "com.android.example.USB_PERMISSION";
        private static final int BAUD_RATE = 230400; // BaudRate. Change this value if you need
        public static boolean SERVICE_CONNECTED = false;

        private IBinder binder = new HLSUsbBinder();

        //adb shell am broadcast -a com.pudutech.lidar.data.error
        //测试方法：adb发送广播
        //程序发送广播
        /*try {
            Intent intent = new Intent();
            intent.setAction(HLSRadarService.ACTION_USB_DATAERROR_RESET);
            App.getInstance().sendBroadcast(intent);
        }catch(Exception e){
            e.printStackTrace();
        }*/
        public static final String ACTION_USB_DATAERROR_RESET = "com.pudutech.lidar.data.error";


    private Context context;
        private Handler mHandler;
        private UsbManager usbManager;
        private UsbDevice device;
        private UsbDeviceConnection connection;
        private UsbSerialDevice serialPort;

        HandlerThread mWorkThread;
        Handler mRadarHandler;

        private boolean serialPortConnected;
        /*
         *  Data received from serial port will be received here. Just populate onReceivedData with your code
         *  In this particular example. byte stream is converted to String and send to UI thread to
         *  be treated there.
         */
        private UsbSerialInterface.UsbReadCallback mCallback = new UsbSerialInterface.UsbReadCallback() {
            @Override
            public void onReceivedData(byte[] arg0) {
                try {
                    if( mRadarHandler != null) {
                        mRadarHandler.obtainMessage(HLS_MESSAGE_FROM_SERIAL_PORT, arg0).sendToTarget();
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        };

        /*
         * State changes in the CTS line will be received here
         */
        private UsbSerialInterface.UsbCTSCallback ctsCallback = new UsbSerialInterface.UsbCTSCallback() {
            @Override
            public void onCTSChanged(boolean state) {
                if(mHandler != null) {
                    mHandler.obtainMessage(CTS_CHANGE).sendToTarget();
                }
            }
        };

        /*
         * State changes in the DSR line will be received here
         */
        private UsbSerialInterface.UsbDSRCallback dsrCallback = new UsbSerialInterface.UsbDSRCallback() {
            @Override
            public void onDSRChanged(boolean state) {
                if(mHandler != null)
                    mHandler.obtainMessage(DSR_CHANGE).sendToTarget();
            }
        };
        /*
         * Different notifications from OS will be received here (USB attached, detached, permission responses...)
         * About BroadcastReceiver: http://developer.android.com/reference/android/content/BroadcastReceiver.html
         */
        private final BroadcastReceiver usbReceiver = new BroadcastReceiver() {
            @Override
            public void onReceive(Context arg0, Intent arg1) {
                if (arg1.getAction().equals(ACTION_USB_PERMISSION)) {
                    boolean granted = arg1.getExtras().getBoolean(UsbManager.EXTRA_PERMISSION_GRANTED);
                    if (granted) // User accepted our USB connection. Try to open the device as a serial port
                    {
                        Intent intent = new Intent(ACTION_USB_PERMISSION_GRANTED);
                        arg0.sendBroadcast(intent);
                        connection = usbManager.openDevice(device);
                        new ConnectionThread().start();
                    } else // User not accepted our USB connection. Send an Intent to the Main Activity
                    {
                        Intent intent = new Intent(ACTION_USB_PERMISSION_NOT_GRANTED);
                        arg0.sendBroadcast(intent);
                    }
                } else if (arg1.getAction().equals(ACTION_USB_ATTACHED)) {
                    if (!serialPortConnected)
                        findSerialPortDevice(); // A USB device has been attached. Try to open it as a Serial port
                } else if (arg1.getAction().equals(ACTION_USB_DETACHED)) {
                    // Usb device was disconnected. send an intent to the Main Activity
                    Intent intent = new Intent(ACTION_USB_DISCONNECTED);
                    arg0.sendBroadcast(intent);
                    if (serialPortConnected) {
                        serialPort.close();
                    }
                    serialPortConnected = false;
                } else if(arg1.getAction().equals(ACTION_USB_DATAERROR_RESET)) {
                    Log.i("fanyp--lidar", "ACTION_USB_DATAERROR_RESET receiver" );
                    RestartLidarWhenError();
                }
            }
        };


        public void RestartLidarWhenError(){
            try{
                if (serialPort != null){
                    serialPort.syncClose();;
                }

                if(device != null) {
                    connection = usbManager.openDevice(device);
                    new ConnectionThread().start();
                }
            }catch(Exception e) {
                e.printStackTrace();
            }
        }

        /*
         * onCreate will be executed when service is started. It configures an IntentFilter to listen for
         * incoming Intents (USB ATTACHED, USB DETACHED...) and it tries to open a serial port.
         */
        @Override
        public void onCreate() {
            this.context = this;
            serialPortConnected = false;
            RadarUsbService.SERVICE_CONNECTED = true;
            usbManager = (UsbManager) getSystemService(Context.USB_SERVICE);

            setFilter();

            findSerialPortDevice();

            if( mWorkThread == null) {
                mWorkThread = new HandlerThread("HLSRadarDataHandle");
                mWorkThread.start();
                HLSRadarDataDealCallback mycallback = new HLSRadarDataDealCallback();
                mRadarHandler = new Handler(mWorkThread.getLooper(), mycallback);
            }
        }



    List<rplidar_node_t> myListdDatas = new ArrayList<rplidar_node_t>();
    List<rplidar_node_t> mySendDatas = new ArrayList<rplidar_node_t>();
    HLSRadarData mRPLidarData = HLSRadarData.getInstance();
        public class HLSRadarDataDealCallback implements Handler.Callback {

            @Override
            public boolean handleMessage(Message msg) {
                switch (msg.what) {
                    case HLSRadarService.HLS_MESSAGE_FROM_SERIAL_PORT:
                        try {
                            byte[] dataFromUsb = (byte[]) msg.obj;

                            List<rplidar_node_t> listdDatas = mRPLidarData.ParserData(dataFromUsb);
                            if(myListdDatas.size() < 360)
                            {
                                myListdDatas.addAll(listdDatas);
                            }
                            else {
                                mySendDatas.clear();
                                mySendDatas.addAll(myListdDatas);
                                if (mHandler != null) {
                                    mHandler.obtainMessage(HLS_MESSAGE_FROM_SERIAL_PORT, mySendDatas).sendToTarget();
                                }

                                try {
                                    if (App.getInstance().mMyLtsServer != null) {

                                        JSONObject myjson = new JSONObject();
                                        JSONArray myarras = new JSONArray();

                                        myjson.put("type", "lidar_scan");
                                        for(int i = 0; i < mySendDatas.size(); i ++)
                                        {
                                            rplidar_node_t obj = mySendDatas.get(i);

                                            JSONObject myvalue = new JSONObject();
                                            myvalue.put("x", (int)obj.ptX);
                                            myvalue.put("y", (int)obj.ptY);
                                            myarras.put(myvalue);
                                        }

                                        myjson.put("data", myarras);

                                        String lidarString = myjson.toString();
                                        //Log.i("cccc", lidarString);

                                        App.getInstance().mMyLtsServer.Send(lidarString);
                                    }
                                }catch(Exception e)
                                {
                                    e.printStackTrace();
                                }

                                mRPLidarData.Isolation(myListdDatas);

                                Lidar.putOrigin(myListdDatas);

                                myListdDatas.clear();

                            }
                        }catch(Exception e) {
                            e.printStackTrace();
                        }
                        break;
                }
                return true;
            }
        }
        /* MUST READ about services
         * http://developer.android.com/guide/components/services.html
         * http://developer.android.com/guide/components/bound-services.html
         */
        @Override
        public IBinder onBind(Intent intent) {
            return binder;
        }

        @Override
        public int onStartCommand(Intent intent, int flags, int startId) {
            return Service.START_NOT_STICKY;
        }

        @Override
        public void onDestroy() {
            super.onDestroy();
            try {
                stopScan();
            }catch(Exception e)
            {
                e.printStackTrace();
            }

            RadarUsbService.SERVICE_CONNECTED = false;
        }

        public void close()
        {
            if (serialPortConnected && serialPort != null) {
                serialPort.close();
            }
            serialPortConnected = false;
        }


        public void PreStartWork()
        {
            stopMotor();
        }

        public void startScan()
        {
            new Thread(new Runnable(){
                @Override
                public void run() {
                    startMotor();

                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    byte[] cmd = new byte[1];
                    cmd[0] = 'b';
                    write(cmd);
                }
            }).start();

        }

        public void stopScan()
        {
            new Thread(new Runnable(){
                @Override
                public void run() {
                    stopMotor();

                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    byte[] cmd = new byte[1];
                    cmd[0] = 'e';
                    write(cmd);
                }
            }).start();

        }

        private void startMotor()
        {
            if (serialPort != null) {
                serialPort.setDTR(false);
            }
        }

        private void stopMotor()
        {
            if (serialPort != null) {
                serialPort.setDTR(true);
            }
        }

        /*
         * This function will be called from MainActivity to write data through Serial Port
         */
        private void write(byte[] data) {
            if (serialPort != null)
                serialPort.write(data);
        }

        public void setHandler(Handler mHandler) {
            this.mHandler = mHandler;
        }

        private void findSerialPortDevice() {
            // This snippet will try to open the first encountered usb device connected, excluding usb root hubs
            HashMap<String, UsbDevice> usbDevices = usbManager.getDeviceList();
            if (!usbDevices.isEmpty()) {
                boolean keep = true;
                for (Map.Entry<String, UsbDevice> entry : usbDevices.entrySet()) {
                    device = entry.getValue();
                    int deviceVID = device.getVendorId();
                    int devicePID = device.getProductId();

                    if (deviceVID != 0x1d6b && (devicePID != 0x0001 && devicePID != 0x0002 && devicePID != 0x0003)) {
                        // There is a device connected to our Android device. Try to open it as a Serial Port.
                        requestUserPermission();
                        keep = false;
                    } else {
                        connection = null;
                        device = null;
                    }

                    if (!keep)
                        break;
                }
                if (!keep) {
                    // There is no USB devices connected (but usb host were listed). Send an intent to MainActivity.
                    Intent intent = new Intent(ACTION_NO_USB);
                    sendBroadcast(intent);
                }
            } else {
                // There is no USB devices connected. Send an intent to MainActivity
                Intent intent = new Intent(ACTION_NO_USB);
                sendBroadcast(intent);
            }
        }

        private void setFilter() {
            IntentFilter filter = new IntentFilter();
            filter.addAction(ACTION_USB_PERMISSION);
            filter.addAction(ACTION_USB_DETACHED);
            filter.addAction(ACTION_USB_ATTACHED);
            filter.addAction(ACTION_USB_DATAERROR_RESET);
            registerReceiver(usbReceiver, filter);
        }

        /*
         * Request user permission. The response will be received in the BroadcastReceiver
         */
        private void requestUserPermission() {
            PendingIntent mPendingIntent = PendingIntent.getBroadcast(this, 0, new Intent(ACTION_USB_PERMISSION), 0);
            usbManager.requestPermission(device, mPendingIntent);
        }

        public class HLSUsbBinder extends Binder {
            public HLSRadarService getService() {
                return HLSRadarService.this;
            }
        }

        /*
         * A simple thread to open a serial port.
         * Although it should be a fast operation. moving usb operations away from UI thread is a good thing.
         */
        private class ConnectionThread extends Thread {
            @Override
            public void run() {
                try {
                    serialPort = UsbSerialDevice.createUsbSerialDevice(device, connection);
                    if (serialPort != null) {
                        if (serialPort.open()) {
                            serialPortConnected = true;
                            serialPort.setBaudRate(BAUD_RATE);
                            serialPort.setDataBits(UsbSerialInterface.DATA_BITS_8);
                            serialPort.setStopBits(UsbSerialInterface.STOP_BITS_1);
                            serialPort.setParity(UsbSerialInterface.PARITY_NONE);
                            /**
                             * Current flow control Options:
                             * UsbSerialInterface.FLOW_CONTROL_OFF
                             * UsbSerialInterface.FLOW_CONTROL_RTS_CTS only for CP2102 and FT232
                             * UsbSerialInterface.FLOW_CONTROL_DSR_DTR only for CP2102 and FT232
                             */
                            serialPort.setFlowControl(UsbSerialInterface.FLOW_CONTROL_OFF);
                            serialPort.read(mCallback);
                            serialPort.getCTS(ctsCallback);
                            serialPort.getDSR(dsrCallback);

                            // Everything went as expected. Send an intent to MainActivity
                            Intent intent = new Intent(ACTION_USB_READY);
                            context.sendBroadcast(intent);

                            PreStartWork();

                            try {
                                startScan();
                            }catch(Exception e)
                            {
                                e.printStackTrace();
                            }
                        } else {
                            // Serial port could not be opened, maybe an I/O error or if CDC driver was chosen, it does not really fit
                            // Send an Intent to Main Activity
                            if (serialPort instanceof CDCSerialDevice) {
                                Intent intent = new Intent(ACTION_CDC_DRIVER_NOT_WORKING);
                                context.sendBroadcast(intent);
                            } else {
                                Intent intent = new Intent(ACTION_USB_DEVICE_NOT_WORKING);
                                context.sendBroadcast(intent);
                            }
                        }
                    } else {
                        // No driver for given device, even generic CDC driver could not be loaded
                        Intent intent = new Intent(ACTION_USB_NOT_SUPPORTED);
                        context.sendBroadcast(intent);
                    }
                }catch(Exception e)
                {
                    e.printStackTrace();
                }
            }
        }
    }

